<?php

declare(strict_types=1);

namespace app\common\service;

use app\common\model\OperationLog;
use think\facade\Request;
use app\common\annotation\NoLog;
use ReflectionClass;
use ReflectionException;

class LogService
{
    protected $startTime;
    protected $logModel;
    protected $extend = [];

    public function __construct()
    {
        $this->startTime = microtime(true);
        $this->logModel = new OperationLog();
    }

    /**
     * 设置扩展信息
     * @param array $data
     */
    public function setExtend(array $data)
    {
        $this->extend = array_merge($this->extend, $data);
    }

    /**
     * 获取浏览器和操作系统信息
     */
    protected function getBrowserAndOs()
    {
        $userAgent = Request::header('user-agent');
        $browser = 'Unknown';
        $os = 'Unknown';

        // 解析浏览器
        if (preg_match('/MSIE/i', $userAgent)) {
            $browser = 'MSIE';
        } elseif (preg_match('/Firefox/i', $userAgent)) {
            $browser = 'Firefox';
        } elseif (preg_match('/Chrome/i', $userAgent)) {
            $browser = 'Chrome';
        } elseif (preg_match('/Safari/i', $userAgent)) {
            $browser = 'Safari';
        } elseif (preg_match('/Opera/i', $userAgent)) {
            $browser = 'Opera';
        }

        // 解析操作系统
        if (preg_match('/win/i', $userAgent)) {
            $os = 'Windows';
        } elseif (preg_match('/mac/i', $userAgent)) {
            $os = 'MAC';
        } elseif (preg_match('/linux/i', $userAgent)) {
            $os = 'Linux';
        } elseif (preg_match('/unix/i', $userAgent)) {
            $os = 'Unix';
        }

        return [$browser, $os];
    }

    /**
     * 获取注解配置
     * @return array|null
     */
    protected function getNoLogConfig(): ?array
    {
        try {
            // 获取当前控制器和方法
            $controller = Request::controller();
            $action = Request::action();
            
            if (empty($controller) || empty($action)) {
                return null;
            }
            
            // 获取完整的控制器类名
            $class = "app\\admin\\controller\\{$controller}";
            
            // 反射获取类和方法
            $reflection = new ReflectionClass($class);
            
            // 获取类的注解
            $classDoc = $reflection->getDocComment();
            if ($classDoc && strpos($classDoc, '@NoLog') !== false) {
                return ['all' => true];
            }
            
            // 获取方法的注解
            $method = $reflection->getMethod($action);
            $methodDoc = $method->getDocComment();
            
            if ($methodDoc && strpos($methodDoc, '@NoLog') !== false) {
                // 解析注解参数
                preg_match('/@NoLog\((.*?)\)/', $methodDoc, $matches);
                $params = [];
                if (!empty($matches[1])) {
                    // 解析参数字符串，例如: params=true,result=true
                    $paramPairs = explode(',', $matches[1]);
                    foreach ($paramPairs as $pair) {
                        list($key, $value) = explode('=', trim($pair));
                        $params[trim($key)] = trim($value) === 'true';
                    }
                }
                
                return [
                    'all' => empty($params),
                    'params' => $params['params'] ?? false,
                    'result' => $params['result'] ?? false,
                    'headers' => $params['headers'] ?? false
                ];
            }
            
            return null;
        } catch (ReflectionException $e) {
            return null;
        } catch (\Throwable $e) {
            return null;
        }
    }

    /**
     * 记录请求日志
     * @param mixed $result 响应结果
     * @param int $code 响应状态码
     * @param string $errorMsg 错误信息
     */
    public function record($result, int $code = 200, string $errorMsg = '')
    {
        // 获取注解配置
        $noLogConfig = $this->getNoLogConfig();
        
        // 如果完全不记录日志
        if ($noLogConfig && ($noLogConfig['all'] ?? false)) {
            return;
        }
        
        // 获取请求参数（根据注解配置）
        $params = [];
        if (!($noLogConfig['params'] ?? false)) {
            $params = Request::param();
            
            // 过滤敏感信息
            if (isset($params['password'])) {
                $params['password'] = '******';
            }
            
            // 处理文件上传
            if (Request::file()) {
                foreach (Request::file() as $key => $file) {
                    $params[$key] = [
                        'name' => $file->getOriginalName(),
                        'type' => $file->getMime(),
                        'size' => $file->getSize()
                    ];
                }
            }
        }
        
        // 获取请求头（根据注解配置）
        $requestHeader = [];
        if (!($noLogConfig['headers'] ?? false)) {
            $requestHeader = Request::header();
            unset($requestHeader['cookie']);
            unset($requestHeader['authorization']);
        }
        
        // 获取响应头（根据注解配置）
        $responseHeader = [];
        if (!($noLogConfig['headers'] ?? false)) {
            foreach (headers_list() as $header) {
                list($key, $value) = explode(':', $header, 2);
                $responseHeader[$key] = trim($value);
            }
        }
        
        // 处理响应结果
        $logResult = $this->handleResult($result, $code, $errorMsg, $noLogConfig);
        
        // 如果是HTML错误，更新状态码和错误信息
        if (is_array($logResult) && isset($logResult['error_type']) && $logResult['error_type'] === 'html') {
            $code = 500;
            $errorMsg = $logResult['error_msg'];
            if (!empty($logResult['error_location'])) {
                $errorMsg .= ' in ' . $logResult['error_location'];
            }
        }
        
        // 计算执行时间
        $costTime = intval((microtime(true) - $this->startTime) * 1000);

        // 获取浏览器和操作系统信息
        list($browser, $os) = $this->getBrowserAndOs();

        // 记录日志
        $this->logModel->save([
            'method' => Request::method(),
            'url' => Request::url(true),
            'ip' => Request::ip(),
            'browser' => $browser,
            'os' => $os,
            'params' => $params,
            'request_header' => $requestHeader,
            'result' => $logResult,
            'response_header' => $responseHeader,
            'code' => $code,
            'error_msg' => $errorMsg,
            'cost_time' => $costTime,
            'extend' => $this->extend
        ]);
    }

    /**
     * 处理响应结果
     * @param mixed $result 响应结果
     * @param int $code 响应状态码
     * @param string $errorMsg 错误信息
     * @param array|null $noLogConfig 注解配置
     * @return mixed
     */
    protected function handleResult($result, int $code, string $errorMsg, ?array $noLogConfig)
    {
        // 如果配置了不记录结果
        if ($noLogConfig['result'] ?? false) {
            return '[已忽略响应内容]';
        }

        // 如果结果是HTML字符串（可能是错误页面）
        if (is_string($result) && $this->isHtml($result)) {
            // 提取错误信息 - 从HTML中提取关键错误信息
            if (preg_match('/<h1>(.*?)<\/h1>/', $result, $matches)) {
                $errorMsg = strip_tags($matches[1] ?? '页面错误');
            }
            
            // 如果没有找到h1标签,尝试从第一个错误信息中提取
            if (empty($errorMsg) && preg_match('/<div class="info">.*?<div><h1>(.*?)<\/h1><\/div>/s', $result, $matches)) {
                $errorMsg = strip_tags($matches[1] ?? '页面错误');
            }

            // 提取错误位置
            $errorLocation = '';
            if (preg_match('/in\s+<a.*?title="(.*?)".*?>/i', $result, $matches)) {
                $errorLocation = $matches[1] ?? '';
            }
            
            return [
                'error_type' => 'html',
                'error_msg' => $errorMsg,
                'error_location' => $errorLocation,
                'error_code' => 500  // HTML错误页面，状态码应该是500
            ];
        }

        // 如果响应结果中包含文件信息，只记录文件基本信息
        if (is_array($result) && isset($result['data'])) {
            $this->handleFileResponse($result['data']);
        }

        return $result;
    }

    /**
     * 判断字符串是否为HTML
     * @param string $string
     * @return bool
     */
    protected function isHtml(string $string): bool
    {
        return preg_match("/<[^<]+>/", $string) != 0;
    }

    /**
     * 处理文件响应数据
     * @param mixed &$data 响应数据
     */
    protected function handleFileResponse(&$data)
    {
        if (is_array($data)) {
            foreach ($data as $key => &$value) {
                if (is_array($value)) {
                    $this->handleFileResponse($value);
                } elseif (is_object($value) && method_exists($value, 'getOriginalName')) {
                    // 如果是文件对象，只记录基本信息
                    $data[$key] = [
                        'name' => $value->getOriginalName(),
                        'type' => $value->getMime(),
                        'size' => $value->getSize(),
                        'path' => '[文件路径已��藏]'
                    ];
                }
            }
        }
    }
}
